#!/usr/bin/env bash

# Usage: run option -h to get help

# BT auto detection
#   Shall we look for white HC-06-USB dongle ?
FINDBTDONGLE=true
#   Shall we look for rfcomm interface ?
FINDBTRFCOMM=true
#   Shall we look for registered BT device ? (Linux only)
FINDBTDIRECT=true

PM3PATH=$(dirname "$0")
EVALENV=""
FULLIMAGE="fullimage.elf"
BOOTIMAGE="bootrom.elf"

#Skip check if --list is used
if [ ! "$1" == "--list" ]; then
    # try pm3 dirs in current repo workdir
    if [ -d "$PM3PATH/client/" ]; then
        if [ -x "$PM3PATH/client/proxmark3" ]; then
            CLIENT="$PM3PATH/client/proxmark3"
        elif [ -x "$PM3PATH/client/build/proxmark3" ]; then
            CLIENT="$PM3PATH/client/build/proxmark3"
        else
            echo >&2 "[!!] In devel workdir but no executable found, did you compile it?"
            exit 1
        fi
    # try install dir
    elif [ -x "$PM3PATH/proxmark3" ]; then
        CLIENT="$PM3PATH/proxmark3"
    else
    # hope it's installed somehow, still not sure where fw images and pm3.py are...
        CLIENT="proxmark3"
    fi
fi

# LeakSanitizer suppressions
if [ -e .lsan_suppressions ]; then
  EVALENV+=" LSAN_OPTIONS=suppressions=.lsan_suppressions"
fi
if [ "$EVALENV" != "" ]; then
  EVALENV="export $EVALENV"
fi
PM3LIST=()
SHOWLIST=false

function get_pm3_list_Linux {
    N=$1
    PM3LIST=()
    if [ ! -c "/dev/tty0" ]; then
        echo >&2 "[!!] Script cannot access /dev/ttyXXX files, insufficient privileges"
        exit 1
    fi
    for DEV in $(find /dev/ttyACM* 2>/dev/null); do
        if command -v udevadm >/dev/null; then
            # WSL1 detection
            if udevadm info -q property -n "$DEV" | grep -q "ID_VENDOR=proxmark.org"; then
                PM3LIST+=("$DEV")
                if [ ${#PM3LIST[*]} -ge "$N" ]; then
                    return
                fi
            fi
        fi
        # WSL2 with usbipd detection - doesn't report same things as WSL1
        if grep -q "proxmark.org" "/sys/class/tty/${DEV#/dev/}/../../../manufacturer" 2>/dev/null; then
            if echo "${PM3LIST[*]}" | grep -qv "${DEV}"; then
                PM3LIST+=("$DEV")
                if [ ${#PM3LIST[*]} -ge "$N" ]; then
                    return
                fi
            fi
        fi
    done
    if $FINDBTDONGLE; then
        # check if the HC-06-USB white dongle is present (still, that doesn't tell us if it's paired with a Proxmark3...)
        for DEV in $(find /dev/ttyUSB* 2>/dev/null); do
            if command -v udevadm >/dev/null; then
                if udevadm info -q property -n "$DEV" | grep -q "ID_MODEL=CP2104_USB_to_UART_Bridge_Controller"; then
                    PM3LIST+=("$DEV")
                    if [ ${#PM3LIST[*]} -ge "$N" ]; then
                        return
                    fi
                fi
            else
                if grep -q "DRIVER=cp210x" "/sys/class/tty/${DEV#/dev/}/../../uevent" 2>/dev/null; then
                    PM3LIST+=("$DEV")
                    if [ ${#PM3LIST[*]} -ge "$N" ]; then
                        return
                    fi
                fi
            fi
        done
    fi
    if $FINDBTRFCOMM; then
        # check if the MAC of a Proxmark3 was bound to a local rfcomm interface
        # (on OSes without deprecated rfcomm and hcitool, the loop will be simply skipped)
        for DEVMAC in $(rfcomm -a 2>/dev/null | grep " 20:19:0[45]" | sed 's/^\(.*\): \([0-9:]*\) .*/\1@\2/'); do
            DEV=${DEVMAC/@*/}
            MAC=${DEVMAC/*@/}
            # check which are Proxmark3 and, side-effect, if they're actually present
            if hcitool name "$MAC" | grep -q "PM3"; then
                PM3LIST+=("/dev/$DEV")
                if [ ${#PM3LIST[*]} -ge "$N" ]; then
                    return
                fi
            fi
        done
    fi
    if $FINDBTDIRECT; then
        # check if the MAC of a Proxmark3 was registered in the known devices
        for MAC in $(dbus-send --system --print-reply --type=method_call --dest='org.bluez' '/' org.freedesktop.DBus.ObjectManager.GetManagedObjects 2>/dev/null|\
                         awk '/"Address"/{getline;gsub(/"/,"",$3);a=$3}/Name/{getline;if (/PM3_RDV4/ || /Proxmark3 SE/) print a}'); do
            PM3LIST+=("bt:$MAC")
        done
        # we don't probe the device so there is no guarantee the device is actually present
    fi
}

function get_pm3_list_macOS {
    N=$1
    PM3LIST=()
    for DEV in $(ioreg -r -c "IOUSBHostDevice" -l | awk -F '"' '
        $2=="USB Vendor Name"{b=($4=="proxmark.org")}
        b==1 && $2=="IODialinDevice"{print $4}'); do
        PM3LIST+=("$DEV")
        if [ ${#PM3LIST[*]} -ge "$N" ]; then
            return
        fi
    done
}

function get_pm3_list_Windows {
    N=$1
    PM3LIST=()

    # Normal SERIAL PORTS (COM)
    for DEV in $($PSHEXE -command "Get-CimInstance -ClassName Win32_serialport | Where-Object {\$_.PNPDeviceID -like '*VID_9AC4&PID_4B8F*' -or \$_.PNPDeviceID -like '*VID_2D2D&PID_504D*'} | Select -expandproperty DeviceID" 2>/dev/null); do

        _comport=$DEV

        #prevent soft bricking when using pm3-flash-all on an outdated bootloader
        if [ $(basename -- "$0") = "pm3-flash-all" ]; then

            line=$($PSHEXE -command "Get-CimInstance -ClassName Win32_serialport | Where-Object {\$_.DeviceID -eq '$_comport'} | Select -expandproperty PNPDeviceID" 2>/dev/null);

            if [[ ! $line =~ ^"USB\VID_9AC4&PID_4B8F\ICEMAN" ]]; then
                echo -e "\033[0;31m[!] Using pm3-flash-all on an oudated bootloader, use pm3-flash-bootrom first!"
                exit 1
            fi
        fi
        PM3LIST+=("$DEV")
        if [ ${#PM3LIST[*]} -ge "$N" ]; then
            return
        fi
    done

    #BT direct SERIAL PORTS (COM)
    if $FINDBTRFCOMM; then

        for DEV in $($PSHEXE -command "Get-CimInstance -ClassName Win32_PnPEntity | Where-Object Caption -like 'Standard Serial over Bluetooth link (COM*' | Select Name" 2>/dev/null); do
            PM3LIST+=("$DEV")
            if [ ${#PM3LIST[*]} -ge "$N" ]; then
                return
            fi
        done
    fi

    #white BT dongle SERIAL PORTS (COM)
    if $FINDBTDONGLE; then

        for DEV in $($PSHEXE -command "Get-CimInstance -ClassName Win32_serialport | Where-Object PNPDeviceID -like '*VID_10C4&PID_EA60*' | Select -expandproperty DeviceID" 2>/dev/null); do
            PM3LIST+=("$DEV")
            if [ ${#PM3LIST[*]} -ge "$N" ]; then
                return
            fi
        done
    fi
}

function get_pm3_list_WSL {
    N=$1
    PM3LIST=()

    # Normal SERIAL PORTS (COM)
    for DEV in $($PSHEXE -command "Get-CimInstance -ClassName Win32_serialport | Where-Object {\$_.PNPDeviceID -like '*VID_9AC4&PID_4B8F*' -or \$_.PNPDeviceID -like '*VID_2D2D&PID_504D*'} | Select -expandproperty DeviceID" 2>/dev/null); do
        DEV=$(echo $DEV | tr -dc '[:print:]')
        _comport=$DEV
        DEV=$(echo $DEV | sed -nr 's#^COM([0-9]+)\b#/dev/ttyS\1#p')
        # ttyS counterpart takes some more time to appear
        if [ -e "$DEV" ]; then

            #prevent soft bricking when using pm3-flash-all on an outdated bootloader
            if [ $(basename -- "$0") = "pm3-flash-all" ]; then
                line=$($PSHEXE -command "Get-CimInstance -ClassName Win32_serialport | Where-Object {\$_.DeviceID -eq '$_comport'} | Select -expandproperty PNPDeviceID" 2>/dev/null | tr -dc '[:print:]');
                if [[ ! $line =~ ^"USB\VID_9AC4&PID_4B8F\ICEMAN" ]]; then
                    echo -e "\033[0;31m[!] Using pm3-flash-all on an oudated bootloader, use pm3-flash-bootrom first!"
                    exit 1
                fi
            fi
            PM3LIST+=("$DEV")
            if [ ! -w "$DEV" ]; then
                echo "[!] Let's give users read/write access to $DEV"
                sudo chmod 666 "$DEV"
            fi
            if [ ${#PM3LIST[*]} -ge "$N" ]; then
                return
            fi
        fi
    done

    #BT direct SERIAL PORTS (COM)
    if $FINDBTRFCOMM; then
        for DEV in $($PSHEXE -command "Get-CimInstance -ClassName Win32_PnPEntity | Where-Object Caption -like 'Standard Serial over Bluetooth link (COM*' | Select Name" 2> /dev/null | sed -nr 's#.*\bCOM([0-9]+)\b.*#/dev/ttyS\1#p'); do
            # ttyS counterpart takes some more time to appear
            if [ -e "$DEV" ]; then
                PM3LIST+=("$DEV")
                if [ ! -w "$DEV" ]; then
                    echo "[!] Let's give users read/write access to $DEV"
                    sudo chmod 666 "$DEV"
                fi
                if [ ${#PM3LIST[*]} -ge "$N" ]; then
                    return
                fi
            fi

        done
    fi

    #white BT dongle SERIAL PORTS (COM)
    if $FINDBTDONGLE; then
        for DEV in $($PSHEXE -command "Get-CimInstance -ClassName Win32_serialport | Where-Object PNPDeviceID -like '*VID_10C4&PID_EA60*' | Select DeviceID" 2>/dev/null | sed -nr 's#^COM([0-9]+)\b#/dev/ttyS\1#p'); do
            # ttyS counterpart takes some more time to appear
            if [ -e "$DEV" ]; then
                PM3LIST+=("$DEV")
                if [ ! -w "$DEV" ]; then
                    echo "[!] Let's give users read/write access to $DEV"
                    sudo chmod 666 "$DEV"
                fi
                if [ ${#PM3LIST[*]} -ge "$N" ]; then
                    return
                fi
            fi

        done
    fi
}

SCRIPT=$(basename -- "$0")

if [ "$SCRIPT" = "pm3" ]; then
  CMD() { eval "$EVALENV"; $CLIENT "$@"; }
  HELP() {
      cat << EOF

Quick helper script for proxmark3 client when working with a Proxmark3 device

Description:
    The usage is the same as for the proxmark3 client, with the following differences:
     * the correct port name will be automatically guessed;
     * the script will wait for a Proxmark3 to be connected (same as option -w of the client).
    You can also specify a first option -n N to access the Nth Proxmark3 connected.
    To see a list of available ports, use --list.

Usage:
    $SCRIPT [-n <N>] [<any other proxmark3 client option>]
    $SCRIPT [--list] [-h|--help] [-hh|--helpclient]
    $SCRIPT [-o|--offline]


Arguments:
    -h/--help        this help
    -hh/--helpclient proxmark3 client help (the script will forward these options)
    --list           list all detected com ports
    -n <N>           connect device referred to the N:th number on the --list output
    -o/--offline     shortcut to use directly the proxmark3 client without guessing ports

Samples:
    ./$SCRIPT                       -- Auto detect/ select com port in the following order BT, USB/CDC, BT DONGLE
    ./$SCRIPT -p /dev/ttyACM0       -- connect to port /dev/ttyACM0
    ./$SCRIPT -n 2                  -- use second item from the --list output
    ./$SCRIPT -c 'lf search' -i     -- run command and stay in client once completed


EOF
  }
elif [ "$SCRIPT" = "pm3-flash" ]; then
  FINDBTDONGLE=false
  FINDBTRFCOMM=false
  FINDBTDIRECT=false
  CMD() {
      ARGS=("--port" "$1" "--flash")
      shift;
      while [ "$1" != "" ]; do
          if [ "$1" == "-b" ]; then
              ARGS+=("--unlock-bootloader")
          elif [ "$1" == "--force" ]; then
              ARGS+=("--force")
          else
              ARGS+=("--image" "$1")
          fi
          shift;
      done
      $CLIENT "${ARGS[@]}";
  }
  HELP() {
      cat << EOF
Quick helper script for flashing a Proxmark3 device via USB

Description:
    The usage is similar to the old proxmark3-flasher binary, except that the correct port name will be automatically guessed.
    You can also specify a first option -n N to access the Nth Proxmark3 connected on USB.
    If this doesn't work, you'll have to use manually the proxmark3 client, see "$CLIENT -h".
    To see a list of available ports, use --list.

Usage:
    $SCRIPT [-n <N>] [-b] image.elf [image.elf...]
    $SCRIPT --list

Options:
    -b         Enable flashing of bootloader area (DANGEROUS)

Example:
     $SCRIPT -b bootrom.elf fullimage.elf
EOF
  }
elif [ "$SCRIPT" = "pm3-flash-all" ]; then
  FINDBTDONGLE=false
  FINDBTRFCOMM=false
  FINDBTDIRECT=false


  CMD() {
      ARGS=("--port" "$1" "--flash" "--unlock-bootloader" "--image" "$BOOTIMAGE" "--image" "$FULLIMAGE")
      shift;
      while [ "$1" != "" ]; do
          if [ "$1" == "--force" ]; then
              ARGS+=("--force")
          fi
          shift;
      done
      $CLIENT "${ARGS[@]}";
  }
  HELP() {
      cat << EOF
Quick helper script for flashing a Proxmark3 device via USB

Description:
    The correct port name will be automatically guessed and the stock bootloader and firmware image will be flashed.
    You can also specify a first option -n N to access the Nth Proxmark3 connected on USB.
    If this doesn't work, you'll have to use manually the proxmark3 client, see "$CLIENT -h".
    To see a list of available ports, use --list.

Usage:
    $SCRIPT [-n <N>]
    $SCRIPT --list
EOF
  }
elif [ "$SCRIPT" = "pm3-flash-fullimage" ]; then
  FINDBTDONGLE=false
  FINDBTRFCOMM=false
  FINDBTDIRECT=false
  CMD() {
      ARGS=("--port" "$1" "--flash" "--image" "$FULLIMAGE")
      shift;
      while [ "$1" != "" ]; do
          if [ "$1" == "--force" ]; then
              ARGS+=("--force")
          fi
          shift;
      done
      $CLIENT "${ARGS[@]}";
  }
  HELP() {
      cat << EOF
Quick helper script for flashing a Proxmark3 device via USB

Description:
    The correct port name will be automatically guessed and the stock firmware image will be flashed.
    You can also specify a first option -n N to access the Nth Proxmark3 connected on USB.
    If this doesn't work, you'll have to use manually the proxmark3 client, see "$CLIENT -h".
    To see a list of available ports, use --list.

Usage:
    $SCRIPT [-n <N>]
    $SCRIPT --list
EOF
  }
elif [ "$SCRIPT" = "pm3-flash-bootrom" ]; then
  FINDBTDONGLE=false
  FINDBTRFCOMM=false
  FINDBTDIRECT=false
  CMD() {
      ARGS=("--port" "$1" "--flash" "--unlock-bootloader" "--image" "$BOOTIMAGE")
      shift;
      while [ "$1" != "" ]; do
          if [ "$1" == "--force" ]; then
              ARGS+=("--force")
          fi
          shift;
      done
      $CLIENT "${ARGS[@]}";
  }
  HELP() {
      cat << EOF
Quick helper script for flashing a Proxmark3 device via USB

Description:
    The correct port name will be automatically guessed and the stock bootloader will be flashed.
    You can also specify a first option -n N to access the Nth Proxmark3 connected on USB.
    If this doesn't work, you'll have to use manually the proxmark3 client, see "$CLIENT -h".
    To see a list of available ports, use --list.

Usage:
    $SCRIPT [-n <N>]
    $SCRIPT --list
EOF
  }
else
  echo >&2 "[!!] Script ran under unknown name, abort: $SCRIPT"
  exit 1
fi

# priority to the help options
for ARG; do
    if [ "$ARG" == "-h" ] || [ "$ARG" == "--help" ]; then
        HELP
        exit 0
    fi
    if [ "$ARG" == "-hh" ] || [ "$ARG" == "--helpclient" ]; then
        CMD "-h"
        exit 0
    fi
done

# if offline, bypass the script and forward all other args
for ARG; do
    shift
    if [ "$ARG" == "-o" ] || [ "$ARG" == "--offline" ]; then
        CMD "$@"
        exit $?
    fi
    set -- "$@" "$ARG"
done

# if a port is already provided, let's just run the command as such
for ARG; do
    shift
    if [ "$ARG" == "-p" ]; then
        CMD "$@"
        exit $?
    fi
    set -- "$@" "$ARG"
done

if [ "$1" == "--list" ]; then
    shift
    if [ "$1" != "" ]; then
        echo >&2 "[!!] Option --list must be used alone"
        exit 1
    fi
    SHOWLIST=true
fi

# Number of the Proxmark3 we're interested in
N=1
if [ "$1" == "-n" ]; then
    shift
    if [ "$1" -ge 1 ] && [ "$1" -lt 10 ]; then
        N=$1
        shift
    else
        echo >&2 "[!!] Option -n requires a number between 1 and 9, got \"$1\""
        exit 1
    fi
fi

HOSTOS=$(uname | awk '{print toupper($0)}')
if [ "$HOSTOS" = "LINUX" ]; then
    # Detect when running under WSL1 (but exclude WSL2)
    if uname -a | grep -qi Microsoft && uname -a | grep -qvi WSL2; then
        # First try finding it using the PATH environment variable
        PSHEXE=$(command -v powershell.exe 2>/dev/null)

        # If it fails (such as if WSLENV is not set), try using the default installation path
        if [ -z "$PSHEXE" ]; then
            PSHEXE=/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe
        fi

        # Finally test if PowerShell is working
        if ! "$PSHEXE" exit >/dev/null 2>&1; then
            echo >&2 "[!!] Cannot run powershell.exe, are you sure your WSL is authorized to run Windows processes? (cf WSL interop flag)"
            exit 1
        fi

        GETPM3LIST=get_pm3_list_WSL
    else
        GETPM3LIST=get_pm3_list_Linux
    fi
elif [ "$HOSTOS" = "DARWIN" ]; then
    GETPM3LIST=get_pm3_list_macOS
elif [[ "$HOSTOS" =~ MINGW(32|64)_NT* ]]; then

    # First try finding it using the PATH environment variable
    PSHEXE=$(command -v powershell.exe 2>/dev/null)

    # If it fails (such as if WSLENV is not set), try using the default installation path
    if [ -z "$PSHEXE" ]; then
        PSHEXE=/cygdrive/c/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe
    fi

    # Finally test if PowerShell is working
    if ! "$PSHEXE" exit >/dev/null 2>&1; then
        echo >&2 "[!!] Cannot run powershell.exe in your MINGW environment"
        exit 1
    fi

    GETPM3LIST=get_pm3_list_Windows
else
    echo >&2 "[!!] Host OS not recognized, abort: $HOSTOS"
    exit 1
fi

if $SHOWLIST; then
    # Probe for up to 9 devs
    $GETPM3LIST 9
    if [ ${#PM3LIST} -lt 1 ]; then
        echo >&2 "[!!] No port found"
        exit 1
    fi
    n=1
    for DEV in "${PM3LIST[@]}"
        do
            echo "$n: $DEV"
            n=$((n+1))
        done
    exit 0
fi

# Wait till we get at least N Proxmark3 devices
$GETPM3LIST "$N"
if [ ${#PM3LIST} -lt "$N" ]; then
    echo >&2 "[=] Waiting for Proxmark3 to appear..."
fi
while true; do
    if [ ${#PM3LIST[*]} -ge "$N" ]; then
        break
    fi
    sleep .1
    $GETPM3LIST "$N"
done

if [ ${#PM3LIST} -lt "$N" ]; then
    HELP() {
      cat << EOF
[!!] No port found, abort

[?] Hint: try '$SCRIPT --list' to see list of available ports,  and use the -n command like below
[?]    $SCRIPT [-n <N>]

EOF
  }
    HELP
    exit 1
fi

CMD "${PM3LIST[$((N-1))]}" "$@"
exit $?
